package com.practice.concurrency.lock;

import java.util.ArrayList;
import java.util.List;

public class FairLock {
	
	private boolean isLocked = false;
	
	private Thread lockingThread = null;
	
	private List<QueueObject> waitingThreadList = new ArrayList<>();
	
	public void lock() throws InterruptedException {
		QueueObject queueObject = new QueueObject();
		boolean isLockedForThisThread = true;
		synchronized (this) {
			waitingThreadList.add(queueObject);
		}
		
		while (isLockedForThisThread) {
			synchronized (this) {
				isLockedForThisThread = 
						isLocked || waitingThreadList.get(0) != queueObject;
				if (!isLockedForThisThread) {
					isLocked = true;
					waitingThreadList.remove(queueObject);
					lockingThread = Thread.currentThread();
					return;
				}
			}
			try {
				queueObject.doWait();
			} catch (InterruptedException ex) {
				synchronized (this) {
					waitingThreadList.remove(queueObject);
				}
				throw ex;
			}
		}
	}
	
	public synchronized void unlock() {
		if (this.lockingThread != Thread.currentThread()) {
			throw new IllegalMonitorStateException(
					"Calling thread has not locked this lock");
		}
		isLocked = false;
		lockingThread = null;
		if (waitingThreadList.size() > 0) {
			waitingThreadList.get(0).doNotify();
		}
	}
}

class QueueObject {
	private boolean isNotified = false;
	
	public synchronized void doWait() throws InterruptedException {
		while (!isNotified) {
			this.wait();
		}
		this.isNotified = false;
	}
	
	public synchronized void doNotify() {
		this.isNotified = true;
		this.notify();
	}
	
	@Override
	public boolean equals(Object obj) {
		return this == obj;
	}
}
